package com.zhouyu.paydemo.unipay.sdk;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.PrivateKey;
import java.util.HashMap;
import java.util.Map;

import static com.zhouyu.paydemo.unipay.sdk.SDKConstants.*;

/**
 * @ClassName QrcService
 * @Description sdk接口服务类，接入商户集成请可以直接参考使用本类中的方法
 * @date 2018-12-10 下午2:44:37 声明：以下代码只是为了方便接入方测试而提供的样例代码，商户可以根据自己需要，按照技术文档编写。该代码仅供参考，不提供编码，性能，规范性等方面的保障
 */
@Slf4j
public class QrcService {

    /**
     * 请求报文签名(使用配置文件中配置的私钥证书)<br> 功能：对请求报文进行签名,并计算赋值certid,signature字段并返回<br>
     *
     * @param reqData  请求报文map<br>
     * @param encoding 上送请求报文域encoding字段的值<br>
     * @return　签名后的map对象<br>
     */
    public static Map<String, String> sign(Map<String, String> reqData, String encoding) {
        reqData = SDKUtil.filterBlank(reqData);
        if (SDKUtil.isEmpty(encoding)) {
            encoding = "UTF-8";
        }
        //设置算法类型（acp_sdk.properties配置文件中配置项acpsdk.signMethod)
        reqData.put("signType", SDKConfig.getConfig().getSignType());

        String signType = reqData.get(param_signType);
        String version = reqData.get(SDKConstants.param_version);
        if (SDKUtil.isEmpty(version)) {
            log.error("version must Not null");
            return reqData;
        }
        if (SDKUtil.isEmpty(signType) || SIGNMETHOD_RSA.equals(signType)) {
            //SHA1withRSA 不送signMethod默认的签名算法
            signSHA1withRSA(reqData, encoding, null, null);
        } else if (SIGNMETHOD_RSA2.equals(signType)) {
            //SHA256withRSA
            signSHA256withRSA(reqData, encoding, null, null);
        } else if (SIGNMETHOD_SM2.equals(signType)) {
            //国密算法，需机构自行通过加密机签名，本sdk不提供

        }
        return reqData;
    }

    /**
     * 多证书签名(通过传入私钥证书路径和密码签名）<br> 功能：如果有多个商户号接入银联,每个商户号对应不同的证书可以使用此方法:传入私钥证书和密码<br>
     *
     * @param reqData  请求报文map<br>
     * @param certPath 签名私钥文件（带路径）<br>
     * @param certPwd  签名私钥密码<br>
     * @param encoding 上送请求报文域encoding字段的值<br>
     * @return　签名后的map对象<br>
     */
    public static Map<String, String> signByCertInfo(Map<String, String> reqData, String certPath,
                                                     String certPwd, String encoding) {
        reqData = SDKUtil.filterBlank(reqData);
        if (SDKUtil.isEmpty(encoding)) {
            encoding = "UTF-8";
        }
        if (SDKUtil.isEmpty(certPath) || SDKUtil.isEmpty(certPwd)) {
            log.error("CertPath or CertPwd is empty");
            return reqData;
        }

        //设置算法类型（acp_sdk.properties配置文件中配置项acpsdk.signMethod)
        reqData.put("signType", SDKConfig.getConfig().getSignType());

        String signType = reqData.get(param_signType);
        String version = reqData.get(SDKConstants.param_version);
        if (SDKUtil.isEmpty(version)) {
            log.error("version must Not null");
            return reqData;
        }
        if (VERSION_1_0_0.equals(version)) {
            if (SDKUtil.isEmpty(signType) || SIGNMETHOD_RSA.equals(signType)) {
                //SHA1withRSA 不送signMethod默认的签名算法
                signSHA1withRSA(reqData, encoding, certPath, certPwd);
            } else if (SIGNMETHOD_RSA2.equals(signType)) {
                //SHA256withRSA
                signSHA256withRSA(reqData, encoding, certPath, certPwd);
            } else if (SIGNMETHOD_SM2.equals(signType)) {
                //国密算法，需机构自行通过加密机签名，本sdk不提供
            }
        }
        return reqData;
    }

    /**
     * 验证签名(SHA-1或SHA-256摘要算法)<br>
     *
     * @param resData  返回报文数据<br>
     * @param encoding 上送请求报文域encoding字段的值<br>
     * @return true 通过 false 未通过<br>
     */
    public static boolean validate(Map<String, String> rspData, String encoding) {
        log.info("验签处理开始");
        if (SDKUtil.isEmpty(encoding)) {
            encoding = "UTF-8";
        }
        String signType = rspData.get(SDKConstants.param_signType);
        String version = rspData.get(SDKConstants.param_version);
        if (VERSION_1_0_0.equals(version)) {
            if (SDKUtil.isEmpty(signType) || SIGNMETHOD_RSA.equals(signType)) {
                //SHA1withRSA 不送signMethod默认的签名算法
                return validateSHA1WithRSA(rspData, encoding);
            } else if (SIGNMETHOD_RSA2.equals(signType)) {
                //SHA256withRSA
                return validateSHA256WithRSA(rspData, encoding);
            } else if (SIGNMETHOD_SM2.equals(signType)) {
                //国密算法，需机构自行通过加密机验签，本sdk不提供
            }
        }
        return false;
    }

    /**
     * 功能：后台交易提交请求报文并接收同步应答报文<br>
     *
     * @param reqData      请求报文<br>
     * @param rspData      应答报文<br>
     * @param reqUrl       请求地址<br>
     * @param encoding<br>
     * @return 应答http 200返回true ,其他false<br>
     */
    public static Map<String, String> post(
            Map<String, String> reqData, String reqUrl, String encoding) {
        Map<String, String> rspData = new HashMap<String, String>();
        log.info("请求银联地址:" + reqUrl);
        //发送后台请求数据,连接超时时间，读超时时间（可自行判断，修改）
        HttpClient hc = new HttpClient(reqUrl, 30000, 30000);
        try {
            int status = hc.send(reqData, encoding);
            if (200 == status) {
                String resultString = hc.getResult();
                if (null != resultString && !"".equals(resultString)) {
                    // 将返回结果转换为map
                    Map<String, String> tmpRspData = SDKUtil.convertResultStringToMap(resultString);
                    rspData.putAll(tmpRspData);
                }
            } else {
                log.info("返回http状态码[" + status + "]，请检查请求报文或者请求地址是否正确");
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return rspData;
    }

    /**
     * 密码加密并做base64<br>
     *
     * @param accNo        卡号<br>
     * @param pwd          密码<br>
     * @param encoding<br>
     * @return 加密的内容<br>
     */
    public static String encryptPin(String accNo, String pin, String encoding) {
        return SecureUtil.encryptPin(accNo, pin, encoding, CertUtil
                .getEncryptCertPublicKey());
    }

    /**
     * 敏感信息加密并做base64(卡号，手机号，cvn2,有效期）<br>
     *
     * @param data         送 phoneNo,cvn2,有效期<br>
     * @param encoding<br>
     * @return 加密的密文<br>
     */
    public static String encryptData(String data, String encoding) {
        return SecureUtil.encryptData(data, encoding, CertUtil
                .getEncryptCertPublicKey());
    }

    /**
     * 敏感信息解密，使用配置文件acp_sdk.properties解密<br>
     *
     * @param base64EncryptedInfo 加密信息<br>
     * @param encoding<br>
     * @return 解密后的明文<br>
     */
    public static String decryptData(String base64EncryptedInfo, String encoding) {
        return SecureUtil.decryptData(base64EncryptedInfo, encoding, CertUtil
                .getSignCertPrivateKey());
    }

    /**
     * 敏感信息解密,通过传入的私钥解密<br>
     *
     * @param base64EncryptedInfo 加密信息<br>
     * @param certPath            私钥文件（带全路径）<br>
     * @param certPwd             私钥密码<br>
     * @param encoding<br>
     * @return
     */
    public static String decryptData(String base64EncryptedInfo, String certPath,
                                     String certPwd, String encoding) {
        return SecureUtil.decryptData(base64EncryptedInfo, encoding, CertUtil
                .getSignCertPrivateKeyByStoreMap(certPath, certPwd));
    }

    /**
     * 获取敏感信息加密证书的物理序列号<br>
     *
     * @return
     */
    public static String getEncryptCertId() {
        return CertUtil.getEncryptCertId();
    }

    /**
     * 对字符串做base64<br>
     *
     * @param rawStr<br>
     * @param encoding<br>
     * @throws IOException
     * @return<br>
     */
    public static String base64Encode(String rawStr, String encoding) throws IOException {
        byte[] rawByte = rawStr.getBytes(encoding);
        return new String(SecureUtil.base64Encode(rawByte), encoding);
    }

    /**
     * 对base64的字符串解base64<br>
     *
     * @param base64Str<br>
     * @param encoding<br>
     * @throws IOException
     * @return<br>
     */
    public static String base64Decode(String base64Str, String encoding) throws IOException {
        byte[] rawByte = base64Str.getBytes(encoding);
        return new String(SecureUtil.base64Decode(rawByte), encoding);
    }

    /**
     * sha1withRsa算法签名
     *
     * @param data
     * @param encoding
     * @param certPath
     * @param certPwd
     */
    private static void signSHA1withRSA(Map<String, String> data, String encoding,
                                        String certPath, String certPwd) {
        String certId;
        PrivateKey pk;
        if (!SDKUtil.isEmpty(certPath)) {
            certId = CertUtil.getCertIdByKeyStoreMap(certPath, certPwd);
            pk = CertUtil.getSignCertPrivateKeyByStoreMap(certPath, certPwd);
        } else {
            certId = CertUtil.getSignCertId();
            pk = CertUtil.getSignCertPrivateKey();
        }
        // 设置签名证书序列号
        data.put(SDKConstants.param_certId, certId);
        // 将Map信息转换成key1=value1&key2=value2的形式
        String stringData = SDKUtil.coverMap2String(data);
        log.info("打印排序后待签名请求报文串（交易返回11验证签名失败时可以用来同正确的进行比对）:[" + stringData + "]");
        byte[] byteSign = null;
        String stringSign = null;
        try {
            // 通过SHA1进行摘要并转16进制
            byte[] signDigest = SecureUtil.sha1X16(stringData, encoding);
            log.info("打印摘要（交易返回11验证签名失败可以用来同正确的进行比对）:[" + new String(signDigest) + "]");
            byteSign = SecureUtil.base64Encode(SecureUtil.signBySoft(pk, signDigest));
            stringSign = new String(byteSign);
            // 设置签名域值
            data.put(SDKConstants.param_signature, stringSign);
        } catch (Exception e) {
            log.error("Sign Error", e);
        }
    }

    /**
     * sha256withRsa算法签名
     *
     * @param data
     * @param encoding
     * @param certPath
     * @param certPwd
     */
    private static void signSHA256withRSA(Map<String, String> data, String encoding,
                                          String certPath, String certPwd) {
        String certId;
        PrivateKey pk;
        if (!SDKUtil.isEmpty(certPath)) {
            certId = CertUtil.getCertIdByKeyStoreMap(certPath, certPwd);
            pk = CertUtil.getSignCertPrivateKeyByStoreMap(certPath, certPwd);
        } else {
            certId = CertUtil.getSignCertId();
            pk = CertUtil.getSignCertPrivateKey();
        }
        // 设置签名证书序列号
        data.put(SDKConstants.param_certId, certId);
        // 将Map信息转换成key1=value1&key2=value2的形式
        String stringData = SDKUtil.coverMap2String(data);
        log.info("打印待签名请求报文串（交易返回11验证签名失败时可以用来同正确的进行比对）:[" + stringData + "]");
        byte[] byteSign = null;
        String stringSign = null;
        try {
            // 通过SHA256进行摘要并转16进制
            byte[] signDigest = SecureUtil.sha256X16(stringData, encoding);
            log.info("打印摘要（交易返回11验证签名失败可以用来同正确的进行比对）:[" + new String(signDigest) + "]");
            byteSign = SecureUtil.base64Encode(SecureUtil.signBySoft256(pk, signDigest));
            stringSign = new String(byteSign);
            // 设置签名域值
            data.put(SDKConstants.param_signature, stringSign);
        } catch (Exception e) {
            log.error("Sign Error", e);
        }
    }

    /**
     * sha1withRsa验签方法
     *
     * @param resData
     * @param encoding
     * @return
     */
    private static boolean validateSHA1WithRSA(Map<String, String> resData, String encoding) {
        String stringSign = resData.get(SDKConstants.param_signature);
        log.info("签名原文：[" + stringSign + "]");
        // 从返回报文中获取certId ，然后去证书静态Map中查询对应验签证书对象
        String certId = resData.get(SDKConstants.param_certId);
        log.info("对返回报文串验签使用的验签公钥序列号：[" + certId + "]");
        // 将Map信息转换成key1=value1&key2=value2的形式
        String stringData = SDKUtil.coverMap2String(resData);
        log.info("待验签返回报文串：[" + stringData + "]");

        try {
            // 验证签名需要用银联发给商户的公钥证书.
            boolean result = SecureUtil.validateSignBySoft(CertUtil
                                                                   .getValidatePublicKey(certId), SecureUtil
                                                                   .base64Decode(stringSign.getBytes(encoding)),
                                                           SecureUtil.sha1X16(stringData, encoding));
            log.info("验证签名" + (result ? "成功" : "失败"));
            return result;
        } catch (UnsupportedEncodingException e) {
            log.error(e.getMessage(), e);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return false;
    }

    /**
     * sha256withRsa验签方法
     *
     * @param resData
     * @param encoding
     * @return
     */
    private static boolean validateSHA256WithRSA(Map<String, String> resData, String encoding) {

        String stringSign = resData.get(SDKConstants.param_signature);
        log.info("签名原文：[" + stringSign + "]");
        String certId = resData.get(SDKConstants.param_certId);
        log.info("对返回报文串验签使用的验签公钥序列号：[" + certId + "]");
        // 将Map信息转换成key1=value1&key2=value2的形式
        String stringData = SDKUtil.coverMap2String(resData);
        log.info("待验签返回报文串：[" + stringData + "]");

        try {
            // 验证签名需要用银联发给商户的公钥证书.
            boolean result = SecureUtil.validateSignBySoft256(CertUtil
                                                                      .getValidatePublicKey(certId),
                                                              SecureUtil.base64Decode(stringSign
                                                                                              .getBytes(encoding)),
                                                              SecureUtil.sha256X16(
                                                                      stringData, encoding));
            log.info("验证签名" + (result ? "成功" : "失败"));
            return result;
        } catch (UnsupportedEncodingException e) {
            log.error(e.getMessage(), e);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return false;
    }

    public static int genLuhn(String number) {
        return SecureUtil.genLuhn(number);
    }
}
